package cn.xinfei.xdecision.data.core.frame.strategy.standard;

import cn.xinfei.xdecision.common.model.datax.consts.VarPrometheusTagsConst;
import cn.xinfei.xdecision.common.model.datax.consts.VariableConst;
import cn.xinfei.xdecision.common.model.datax.datamanage.v2.Variable;
import cn.xinfei.xdecision.common.model.datax.enums.EnumUtil;
import cn.xinfei.xdecision.common.model.datax.enums.ExecutorType;
import cn.xinfei.xdecision.common.model.datax.enums.RetryStrategy;
import cn.xinfei.xdecision.common.model.datax.enums.VariableStrategyType;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataException;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataExceptionType;
import cn.xinfei.xdecision.common.utils.util.runner.DictVariableUtils;
import cn.xinfei.xdecision.data.model.IVariableRequest;
import cn.xinfei.xdecision.data.model.IVariableResponse;
import cn.xinfei.xdecision.data.core.cache.VarMetaDataCache;
import cn.xinfei.xdecision.data.core.frame.executors.collect.VariableHelper;
import cn.xinfei.xdecision.data.core.frame.retry.HttpClientManage;
import cn.xinfei.xdecision.data.core.frame.strategy.BaseStrategy;
import cn.xinfei.xdecision.data.core.frame.strategy.StrategyHandler;
import cn.xinfei.xdecision.data.core.vo.VarInterfaceInfoVO;
import cn.xinfei.xdecision.data.core.vo.standard.HttpStandardVarRequest;
import cn.xinfei.xdecision.data.core.vo.standard.HttpStandardVarResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Transaction;
import com.google.common.collect.Maps;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@StrategyHandler(type = VariableStrategyType.HTTP_STAND)
public class HttpStandardStrategy extends BaseStrategy {

    private final Logger LOGGER = LoggerFactory.getLogger(HttpStandardStrategy.class);

    @Autowired
    VarMetaDataCache varMetaDataCache;


    @Value("${xdecision.data.okhttp.maxIdle}")
    private int maxIdle;

    @Value("${xdecision.data.okhttp.keepAliveDuration}")
    private long keepAliveDuration;

    @Override
    public IVariableResponse process(IVariableRequest request) {
        long begin = System.currentTimeMillis();
        Transaction catT = Cat.newTransaction("interface-datasource", "http-standard");
        HttpStandardVarRequest varRequest = (HttpStandardVarRequest) request;
        checkStandardInterfaceInput(varRequest);

        HttpStandardVarResponse varResponse = new HttpStandardVarResponse();
        varResponse.setRequestId(varRequest.getRequestId());
        ExecutorType execorType = varRequest.getExecorType();
        VarInterfaceInfoVO varInterfaceInfoVO = varRequest.getVarInterfaceInfoVO();
        JSONObject inputParams = varRequest.getInputParams();
        MDC.put(VariableConst.REQUEST_ID, request.requestId());
        if (!request.requestStrategys().contains(VariableStrategyType.HTTP_STAND)) {
            LOGGER.info("request = {} 没有包含 [Http_Standard] request", JSONObject.toJSONString(request));
        } else {
            if (null == execorType) {
                execorType = ExecutorType.SYNC;
            }
            String response = null;
            String requestBody;
            String requestUrl = varInterfaceInfoVO.getUrl();
            RetryStrategy retryStrategy = EnumUtil.fromCodeOrThrow(RetryStrategy.class, varInterfaceInfoVO.getRetryStrategy());
            Integer retryNum = varInterfaceInfoVO.getRetryNum();
            long connectTimeout = varInterfaceInfoVO.getConnectTimeout();
            long readTimeout = varInterfaceInfoVO.getReadTimeout();
            long writeTimeout = varInterfaceInfoVO.getWriteTimeout();

            String interfaceName = varInterfaceInfoVO.getName();
            Integer interfaceId = varInterfaceInfoVO.getId();

            Map<String, Variable> varMetaDataCacheProvider = varMetaDataCache.loadVarMetaData();

            Map<String, Object> inputParam = buildInputFromInterfaceInfoVO(varInterfaceInfoVO, inputParams);

            // 请求参数中的变量赋值
            requestBody = setRequestBodyParams(varInterfaceInfoVO.getRequestBody(), inputParam, varMetaDataCacheProvider);

            // 设置请求头
            Map headers = JSONObject.parseObject(varInterfaceInfoVO.getRequestHeaders(), Map.class);
            Map<String, String> rpcContextMap = buildVarContext(inputParams);
            headers.putAll(rpcContextMap);

            HttpClientManage okHttpClientManage = HttpClientManage.getOkHttpClientManageRetryable(
                    requestUrl, connectTimeout, readTimeout, writeTimeout, retryStrategy, retryNum, maxIdle, keepAliveDuration);
            if (HttpMethod.POST.name().equals(varInterfaceInfoVO.getMethod())) {
                // 发送请求
                if (ExecutorType.SYNC == execorType) {
                    try {
                        response = okHttpClientManage.post(requestBody, HttpClientManage.MEDIATYPE_JSON, headers);
                        if (StringUtils.isBlank(response)) {
                            LOGGER.error("requestId = {} gatewayResponse is empty, inputParam: {}", varRequest.getRequestId(), JSONObject.toJSONString(inputParam));
                            throw new XDecisionDataException(XDecisionDataExceptionType.DATA_SYNC_REQUEST_GATEEAY_EMPTY_ERROR, "同步post请求外部数据源 返回值为空！");
                        }
                        catT.setStatus(Transaction.SUCCESS);
                        varResponse.setData(JSON.parseObject(response, Map.class));
                        Metrics.counter(VarPrometheusTagsConst.STANDARD_DATASOURCE_SUCCESS_COUNT,
                                VarPrometheusTagsConst.MetricsFliedTags.REQUEST_URL, requestUrl,
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_ID, interfaceId.toString(),
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_NAME, interfaceName).increment();
                    } catch (Exception e) {
                        Cat.logError(e);
                        catT.setStatus(e);
                        LOGGER.error("error in sync post request = {}", JSON.toJSONString(inputParam));
                        Metrics.counter(VarPrometheusTagsConst.STANDARD_DATASOURCE_FAIL_COUNT,
                                VarPrometheusTagsConst.MetricsFliedTags.REQUEST_URL, requestUrl,
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_ID, interfaceId.toString(),
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_NAME, interfaceName).increment();
                        throw new XDecisionDataException(XDecisionDataExceptionType.DATA_SYNC_REQUEST_GATEEAY_ERROR,
                                "[同步][POST] 外部数据源 =  " + interfaceName + " 响应异常! reqUrl = " + requestUrl, e);
                    } finally {
                        long end = System.currentTimeMillis();
                        catT.complete();

                        Metrics.counter(VarPrometheusTagsConst.STANDARD_DATASOURCE_REQUEST_COUNT,
                                VarPrometheusTagsConst.MetricsFliedTags.REQUEST_URL, requestUrl,
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_ID, interfaceId.toString(),
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_NAME, interfaceName).increment();

                        Set<Tag> tagList = new HashSet<>();
                        tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.REQUEST_URL, requestUrl));
                        tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_ID, interfaceId.toString()));
                        tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_NAME, interfaceName));
                        Metrics.gauge(VarPrometheusTagsConst.STANDARD_DATASOURCE_DURATION, tagList, end - begin);
                    }

                } else {
                    LOGGER.info("requestId = {}  do not support async request, inputParam: {}", varRequest.getRequestId(), JSONObject.toJSONString(inputParam));
                }
                LOGGER.info("requestId = {} [Http_Standard][Post] 接口源完成 url: {},requestMethod: {}, request: {}, response: {}",
                        varRequest.getRequestId(),
                        requestUrl, varInterfaceInfoVO.getMethod(), requestBody, response);

            } else if (HttpMethod.GET.name().equals(varInterfaceInfoVO.getMethod())) {
                // 封装uri地址路径变量
                Map<String, String> uriVariables = new HashMap<>();
                if (StringUtils.isNotBlank(requestBody)) {
                    uriVariables = JSONObject.parseObject(requestBody, Map.class);
                }
                if (ExecutorType.SYNC == execorType) {
                    try {
                        response = okHttpClientManage.get(headers, uriVariables);
                        if (StringUtils.isBlank(response)) {
                            LOGGER.error("requestId = {} gatewayResponse is empty, inputParam: {}",
                                    varRequest.getRequestId(),
                                    JSONObject.toJSONString(inputParam));
                            throw new XDecisionDataException(XDecisionDataExceptionType.DATA_SYNC_REQUEST_GATEEAY_EMPTY_ERROR, "同步get请求外部数据源 返回值为空!");
                        }
                        catT.setStatus(Transaction.SUCCESS);
                        varResponse.setData(JSON.parseObject(response, Map.class));
                        Metrics.counter(VarPrometheusTagsConst.STANDARD_DATASOURCE_SUCCESS_COUNT,
                                VarPrometheusTagsConst.MetricsFliedTags.REQUEST_URL, requestUrl,
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_ID, interfaceId.toString(),
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_NAME, interfaceName).increment();
                    } catch (Exception e) {
                        Cat.logError(e);
                        catT.setStatus(e);
                        LOGGER.error("error in sync get request = {}", JSON.toJSONString(inputParam));
                        Metrics.counter(VarPrometheusTagsConst.STANDARD_DATASOURCE_FAIL_COUNT,
                                VarPrometheusTagsConst.MetricsFliedTags.REQUEST_URL, requestUrl,
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_ID, interfaceId.toString(),
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_NAME, interfaceName).increment();
                        throw new XDecisionDataException(XDecisionDataExceptionType.DATA_SYNC_REQUEST_GATEEAY_ERROR,
                                "[同步][GET] 外部数据源 =  " + interfaceName + " 响应异常! reqUrl = " + requestUrl, e);
                    } finally {
                        long end = System.currentTimeMillis();
                        catT.complete();
                        Metrics.counter(VarPrometheusTagsConst.STANDARD_DATASOURCE_REQUEST_COUNT,
                                VarPrometheusTagsConst.MetricsFliedTags.REQUEST_URL, requestUrl,
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_ID, interfaceId.toString(),
                                VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_NAME, interfaceName).increment();

                        Set<Tag> tagList = new HashSet<>();
                        tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.REQUEST_URL, requestUrl));
                        tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_ID, interfaceId.toString()));
                        tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.DATASOURCE_NAME, interfaceName));
                        Metrics.gauge(VarPrometheusTagsConst.STANDARD_DATASOURCE_DURATION, tagList, end - begin);
                    }
                } else {
                    LOGGER.info("requestId = {} do not support async request, inputParam: {}",
                            varRequest.getRequestId(),
                            JSONObject.toJSONString(inputParam));
                }
                LOGGER.info("requestId = {} [Http_Standard][Get] 接口源完成 url ={},requestMethod ={}, request ={}, response ={}",
                        varRequest.getRequestId(),
                        requestUrl,
                        varInterfaceInfoVO.getMethod(),
                        requestBody,
                        response);

            } else {
                LOGGER.error("requestId ={} 接口请求类型不匹配当前 Get or Post请求!", varRequest.getRequestId());
                throw new XDecisionDataException(XDecisionDataExceptionType.DATA_NO_SUCH_REQUEST_ERROR, "no such type requset for: " + varInterfaceInfoVO.getMethod());
            }

        }

        return varResponse;
    }


    private String setRequestBodyParams(String requestBody, Map<String, Object> inputParam, Map<String, Variable> varMetaDataCache) {
        if (StringUtils.isBlank(requestBody) || inputParam == null) {
            return "";
        }
        // 添加动态参数
        Pattern pattern = Pattern.compile("\\{[a-zA-Z0-9_\u4e00-\u9fa5()（）-]+\\}");
        Matcher matcher = pattern.matcher(requestBody);
        while (matcher.find()) {
            String matchStr = matcher.group(0);
            String param = matchStr.replace("{", "").replace("}", "");
            // 动态参数从inputParam中获取
            Variable var = varMetaDataCache.get(param);
            LOGGER.info("解析动态参数 varCode = {} varValueType = {}", var.getVarCode(), var.getVarValueType());
            Object requestParam = VariableHelper.toInterfaceRequestParam(var, inputParam.get(param));
            requestBody = requestBody.replace(matchStr, (CharSequence) requestParam);
        }
        return requestBody;
    }


    public Map<String, Object> buildInputFromInterfaceInfoVO(VarInterfaceInfoVO interfaceInfo, JSONObject inputParams) {
        Map<String, Object> inputParam = Maps.newHashMap();
        String bindParam = interfaceInfo.getBindParam();
        if (StringUtils.isNotBlank(bindParam)) {
            JSONObject param = JSON.parseObject(bindParam);
            JSONArray dictVariable = param.getJSONArray("dictVariable");
            for (int i = 0; i < dictVariable.size(); i++) {
                JSONObject jsonObject = dictVariable.getJSONObject(i);
                Object value = DictVariableUtils.getValueFromJsonObject(jsonObject);
                inputParam.put(jsonObject.getString("key"), value);
            }
            JSONArray variable = param.getJSONArray("variable");
            for (int i = 0; i < variable.size(); i++) {
                JSONObject jsonObject = variable.getJSONObject(i);
                if (!inputParam.containsKey(jsonObject.getString("key"))) {
                    inputParam.put(jsonObject.getString("key"), inputParams.getString(jsonObject.getString("key")));
                }
            }

        }
        LOGGER.info("build bind inputParam: {} ", JSONObject.toJSONString(inputParam));
        return inputParam;
    }
}
